package se19team1e.controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

import se19team1e.dao.abstrakt.jaas.AdvertiserDAO;
import se19team1e.dao.abstrakt.jaas.CompanyDAO;
import se19team1e.dao.abstrakt.jaas.DepartmentDAO;
import se19team1e.dao.abstrakt.jaas.JobAdvertDAO;
import se19team1e.dao.factory.DAOFactory;
import se19team1e.dao.factory.DAOFactorySelector;
import se19team1e.entity.AccountDTO;
import se19team1e.entity.AdvertiserDTO;
import se19team1e.entity.CompanyDTO;
import se19team1e.entity.DepartmentDTO;
import se19team1e.entity.JobAdvertDTO;
import se19team1e.exception.DataAccessException;
import se19team1e.gui.CreateJobAdvertForm;
import se19team1e.search.SearchEngine;
import se19team1e.search.SearchEngineFactory;
import se19team1e.util.StringUtil;

/**
 * 
 * The create job advert controller class coordinates the operations between the
 * screen, form, and entity objects for login.
 * 
 * 
 * @author lai shiaw san
 * 
 */
public class CreateJobAdvertController extends BaseController {

	/**
	 * Constructs the controller by calling the super class BaseController Base
	 * controller common functionality is a display message box
	 * 
	 * @param application
	 *            Reference to the current application instance.
	 */
	public CreateJobAdvertController(Application application) {
		super(application);
	}

	/**
	 * Initializes the controller.
	 * 
	 * <p>
	 * The create job advert form is initialized and set as the current active
	 * screen.
	 * </p>
	 * <p>
	 * Initialization involves the retrieval of all companies and departments in
	 * the system.
	 * </p>
	 * 
	 * @throws DataAccessException
	 *             Error retrieving companies and departments.
	 */
	public void initialize() throws DataAccessException {
		DAOFactory daoFactory = DAOFactorySelector.getDAOFactory();

		// get all companies
		CompanyDAO companyDAO = daoFactory.getCompanyDAO();
		List<CompanyDTO> companies = companyDAO.retrieveAll();

		// get all departments
		DepartmentDAO departmentDAO = daoFactory.getDepartmentDAO();
		List<DepartmentDTO> departments = departmentDAO.retrieveAll();

		// get screen
		CreateJobAdvertForm screen = this.getApplication().getScreen(
				CreateJobAdvertForm.class);

		// initialize screen
		screen.initialize(this, companies, departments);

		// set as active screen
		this.getApplication().setCurrentScreen(screen);
	}

	/**
	 * This function performs the following: 1. Validates the job advert, 2.
	 * updated the ownership of the Job Advertiser and the Job Advert 3. saves
	 * to persistence storage 4. Submit the job advert to the search engine
	 * 
	 * 
	 * @param jobAdvert
	 *            A Job advert DTO is use to create.
	 * @throws DataAccessException
	 */
	public void createJobAdvert(JobAdvertDTO jobAdvert)
			throws DataAccessException {
		// validate job advert first
		if (!this.validate(jobAdvert)) {
			// If there is failure in validating the job advert, return control
			// to calling function
			return;
		}

		this.updateJobAdvertAdvertiserOwnership(jobAdvert);

		this.saveJobAdvert(jobAdvert);

		this.submitJobAdvertToSearchEngine(jobAdvert);

		// display success message
		this.displayCreateSuccessMessage();
	}

	/**
	 * Shows the create job advert form again.
	 * 
	 * <p>
	 * This time, only the form is set as the active screen, and no furthur
	 * initialization is asked to be done.
	 * </p>
	 * <p>
	 * This is to retain the previous entered values, in case the user had
	 * wanted to enter multiple similar job adverts at the same go.
	 * </p>
	 */
	public void showCreateJobAdvertForm() {
		// get screen
		CreateJobAdvertForm screen = this.getApplication().getScreen(
				CreateJobAdvertForm.class);

		// no need to reinitialize, reuse same content.

		// set as active screen
		this.getApplication().setCurrentScreen(screen);
	}

	/**
	 * Validates the job advert to see if it is valid for creating.
	 * 
	 * <p>
	 * Returns false if validation fails.
	 * </p>
	 * <p>
	 * Displays an error message if it fails, using a message box.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to create.
	 * @return True if valid.
	 */
	private boolean validate(JobAdvertDTO jobAdvert) {
		return (validateTitle(jobAdvert) && validateDescription(jobAdvert)
				&& validateCompany(jobAdvert) && validateDepartment(jobAdvert)
				&& validateExperience(jobAdvert) && validateLocation(jobAdvert)
				&& validateSalary(jobAdvert) && validateSkills(jobAdvert));
	}

	/**
	 * Update the job advert to belong to the current logged in user's
	 * advertiser id.
	 * 
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job Advert to update.
	 */
	void updateJobAdvertAdvertiserOwnership(JobAdvertDTO jobAdvert) {
		DAOFactory daoFactory = DAOFactorySelector.getDAOFactory();

		// get current account (advertiser info)
		AccountDTO account = this.getApplication().getCurrentAccount();
		String accountId = account.getId();

		// get current advertiser info
		AdvertiserDAO advertiserDAO = daoFactory.getAdvertiserDAO();
		AdvertiserDTO advertiser = null;
		try {
			advertiser = advertiserDAO.retrieveById(accountId);
		} catch (DataAccessException e) {
			e.printStackTrace();
		}

		// must be an advertiser here! no need to check for null!

		// update advertiser id
		if (advertiser != null)
			jobAdvert.setAdvertiserId(advertiser.getId());
	}

	/**
	 * Save the job advert to the persistence storage.
	 * 
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job Advert to save.
	 * @throws DataAccessException
	 *             If error saving the job advert.
	 */
	void saveJobAdvert(JobAdvertDTO jobAdvert) throws DataAccessException {
		DAOFactory daoFactory = DAOFactorySelector.getDAOFactory();

		// save job advert
		JobAdvertDAO jobAdvertDAO = daoFactory.getJobAdvertDAO();
		jobAdvertDAO.create(jobAdvert);
	}

	/**
	 * Submit the job advert to the search engine.
	 * 
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job Advert to submit to search engine.
	 */
	void submitJobAdvertToSearchEngine(JobAdvertDTO jobAdvert) {
		// submit to search engine
		SearchEngine searchEngine = getApplication().getSearchEngine();
		searchEngine.addToIndex(jobAdvert);
	}

	/**
	 * Validates only the description of the job advert.
	 * 
	 * <p>
	 * Ensure that the description is entered.
	 * </p>
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 */
	boolean validateDescription(JobAdvertDTO jobAdvert) {
		if (StringUtil.isEmptyOrNull(jobAdvert.getDescription())) {
			// failure, display error message
			this.displayMissingInputErrorMessage("description");

			return false;
		}
		return true;
	}

	/**
	 * Validates only the company of the job advert.
	 * 
	 * <p>
	 * Ensure that the company is entered.
	 * </p>
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 */
	boolean validateCompany(JobAdvertDTO jobAdvert) {
		if (StringUtil.isEmptyOrNull(jobAdvert.getCompanyId())) {
			// failure, display error message
			this.displayMissingInputErrorMessage("company");

			return false;
		}
		return true;
	}

	/**
	 * Validates only the department of the job advert.
	 * 
	 * <p>
	 * Allows department to be blank, but if it is entered, job advert department should should 
	 * exist at the parent company.
	 * </p>
	 * 
	 * <p>
	 * DAOFactory selector will use the dept DAO and attempt to do a retrieve by ID 
	 * </p>
	 * 
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 * @exception Possible exception if there is no department associated with the company
	 */
	boolean validateDepartment(JobAdvertDTO jobAdvert) {
		// allow no department to be selected
		// but if department is selected, make sure that it belongs to the
		// selected company!
		if (!StringUtil.isEmptyOrNull(jobAdvert.getDepartmentId())) {
			// validate department belongs to company
			DAOFactory daoFactory = DAOFactorySelector.getDAOFactory();
			DepartmentDAO departmentDAO = daoFactory.getDepartmentDAO();
			DepartmentDTO department = null;
			try {
				department = (DepartmentDTO) departmentDAO
						.retrieveById(jobAdvert.getDepartmentId());
			} catch (DataAccessException e1) {
				e1.printStackTrace();
			}
			// If the department is not null
			if (department != null) {
				// Check that the department company ID field matches the job advert company id field
				if (!jobAdvert.getCompanyId().equals(department.getCompanyId())) {
					// failure, display error message
					this.displayMessage("Error",
							"Selected department does not belong to company!",
							new ActionListener() {
								@Override
								public void actionPerformed(ActionEvent e) {
									// do nothing
								}
							});
					//if it fails to match the company ID, return false
					return false;
				}
			}

		}

		return true;
	}

	/**
	 * Validates only the experience of the job advert.
	 * 
	 * <p>
	 * Ensure that the experience is entered.
	 * It should not be empty or null.
	 * </p>
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 */
	boolean validateExperience(JobAdvertDTO jobAdvert) {
		if (StringUtil.isEmptyOrNull(jobAdvert.getExperience())) {
			// failure, display error message
			this.displayMissingInputErrorMessage("experience");

			return false;
		}
		return true;
	}

	/**
	 * Validates only the location of the job advert.
	 * 
	 * <p>
	 * Ensure that the location is entered.
	 * It should not be empty or null.
	 * </p>
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 */
	boolean validateLocation(JobAdvertDTO jobAdvert) {
		if (StringUtil.isEmptyOrNull(jobAdvert.getLocation())) {
			// failure, display error message
			this.displayMissingInputErrorMessage("location");

			return false;
		}
		return true;
	}

	/**
	 * Validates only the salary of the job advert.
	 * 
	 * <p>
	 * Ensure that the salary is greater than 0.
	 * No maximum salary is check.
	 * </p>
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 */
	boolean validateSalary(JobAdvertDTO jobAdvert) {
		if (jobAdvert.getSalary() <= 0) {
			// failure, display error message
			this.displayMessage("Error", "Enter a salary greater than 0",
					new ActionListener() {
						@Override
						public void actionPerformed(ActionEvent e) {
							// do nothing
						}
					});

			return false;
		}
		return true;
	}

	/**
	 * Validates only the skills of the job advert.
	 * 
	 * <p>
	 * Ensure that the skills is entered.
	 * It should not be empty or null.
	 * </p>
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 */
	boolean validateSkills(JobAdvertDTO jobAdvert) {
		if (StringUtil.isEmptyOrNull(jobAdvert.getSkills())) {
			// failure, display error message
			this.displayMissingInputErrorMessage("skills");

			return false;
		}
		return true;
	}

	/**
	 * Validates only the title of the job advert.
	 * 
	 * <p>
	 * Ensure that the title is entered. 
	 * It should not be empty or null.
	 * </p>
	 * <p>
	 * This method is set to package level access for unit testing purpose.
	 * </p>
	 * 
	 * @param jobAdvert
	 *            Job advert to validate.
	 * @return True if valid.
	 */
	boolean validateTitle(JobAdvertDTO jobAdvert) {
		if (StringUtil.isEmptyOrNull(jobAdvert.getTitle())) {
			// failure, display error message
			this.displayMissingInputErrorMessage("title");

			return false;
		}
		return true;
	}

	/**
	 * Displays a message box indicating that creation of job advert is success
	 */
	private void displayCreateSuccessMessage() {
		this.displayMessage("Success", "Job Advert Created",
				new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent event) {
						// show the input screen again
						showCreateJobAdvertForm();
					}
				});
	}

	/**
	 * Displays a message box indicating that input is missing.
	 * @param field that has the the missing input
	 */
	protected void displayMissingInputErrorMessage(String field) {
		this.displayMessage("Error", "Missing " + field, new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent event) {
				// do nothing
			}
		});
	}

}
